﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NetalBase.Reflection;
using System.Diagnostics.Contracts;
using System.Diagnostics;

namespace NetalBase.Base
{
	public abstract class AutomatonBase<IAutomaton, TEventPoint, TDataPoint> : IAutomatonBase
		where TEventPoint : class
		where TDataPoint : class, new()
	{
		#region Fields
		static int _id;
		Dictionary<IStateBase, Dictionary<string, IStateBase>> _trans;
		IList<Type> _finalStates;
		int _automatonId;
		#endregion

		#region Public members
		public AutomatonBase()
		{
			Debug.Assert(this is TEventPoint);

			_trans = new Dictionary<IStateBase, Dictionary<string, IStateBase>>();
			_finalStates = new List<Type>();
			_automatonId = _id++;
			Events2RelatedAutomata = new List<Action>();
			EventsQueue = new List<Action>();
			EventPoint = this as TEventPoint;
			DataPoint = new TDataPoint();
		}

		[Pure]
		public bool InState<TState>() where TState : StateBase<TEventPoint, TDataPoint>
		{
			return typeof(TState) == State.GetType();
		}

		[Pure]
		public bool InOneOfStates<TState1, TState2>()
			where TState1 : StateBase<TEventPoint, TDataPoint>
			where TState2 : StateBase<TEventPoint, TDataPoint>
		{
			return InState<TState1>() || InState<TState2>();
		}

		[Pure]
		public bool InOneOfStates<TState1, TState2, TState3>()
			where TState1 : StateBase<TEventPoint, TDataPoint>
			where TState2 : StateBase<TEventPoint, TDataPoint>
			where TState3 : StateBase<TEventPoint, TDataPoint>
		{
			return InState<TState1>() || InState<TState2>() || InState<TState3>();
		}
		#endregion

		#region IAutomatonBase implementation
		public void ToNextState(string eventName)
		{
			State = getNextState(State, eventName);
		}

		public void CallCurrentStateAction()
		{
			(State as StateBase<TEventPoint, TDataPoint>).StateAction(EventPoint);
		}

		public IList<Action> Events2RelatedAutomata { get; private set; }

		public IList<Action> EventsQueue { get; private set; }

		public IStateBase State { get; private set; }

		public string StringId
		{
			get { return typeof(IAutomaton).Name + _automatonId.ToString(); }
		}

		public bool InFinalState
		{
			get
			{
				return _finalStates.Any(fs => fs == State.GetType());
			}
		}

		public void ProcessRelatedEvents()
		{
			while (Events2RelatedAutomata.Count > 0)
			{
				var relatedEvent = Events2RelatedAutomata[0];
				Events2RelatedAutomata.RemoveAt(0);
				relatedEvent();
			}
		}
		#endregion

		#region Protected members
		protected TEventPoint EventPoint { get; set; }

		protected TDataPoint DataPoint { get; set; }

		protected IStateBase getNextState(IStateBase currentState, string eventName)
		{
			return _trans[currentState][eventName];
		}

		protected void s_s<TStateFrom, TStateTo>(TStateFrom fromState, TStateTo toState)
			where TStateFrom : StateBase<TEventPoint, TDataPoint>, IAutomaton
			where TStateTo : StateBase<TEventPoint, TDataPoint>, IAutomaton
		{
			Contract.Requires(InState<TStateFrom>());

			if (!_trans.ContainsKey(fromState))
				_trans.Add(fromState, new Dictionary<string, IStateBase>());

			var eventName = ReflectionHelper.Get2CallingMethod();
			var fromStateEvents = _trans[fromState];
			if (!fromStateEvents.ContainsKey(eventName))
				fromStateEvents.Add(eventName, toState);
		}

		protected TState createInitState<TState>()
			where TState : StateBase<TEventPoint, TDataPoint>, IAutomaton, new()
		{
			return createStatePrivate<TState>(true, false);
		}

		protected TState createFinalState<TState>()
			where TState : StateBase<TEventPoint, TDataPoint>, IAutomaton, new()
		{
			return createStatePrivate<TState>(false, true);
		}

		protected TState createInitFinalState<TState>()
			where TState : StateBase<TEventPoint, TDataPoint>, IAutomaton, new()
		{
			return createStatePrivate<TState>(true, true);
		}

		protected TState createState<TState>()
			where TState : StateBase<TEventPoint, TDataPoint>, IAutomaton, new()
		{
			return createStatePrivate<TState>(false, false);
		}

		TState createStatePrivate<TState>(bool setInitState = false, bool setFinalState = false)
			where TState : StateBase<TEventPoint, TDataPoint>, IAutomaton, new()
		{
			var state = new TState();
			state.Automaton = this;
			state.DataPoint = DataPoint;

			if (setInitState)
			{
				Debug.Assert(State == null, "Initial state is already set");
				State = state;
				CallCurrentStateAction();
			}

			if (setFinalState)
				_finalStates.Add(typeof(TState));

			return state;
		}
		#endregion
	}
}
